package com.xie.java_doc_searcher.searcher;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xie.java_doc_searcher.config.Config;
import com.xie.java_doc_searcher.model.DocInfo;
import com.xie.java_doc_searcher.model.Weight;
import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.ToAnalysis;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by xiebang
 * Description
 * User:小谢
 * Date:2022-06-30
 * Time:20:17
 */
// 通过这个类在内存中来构造出索引结构

public class Index {
    public static String INDEX_PATH = null;

    static {
        if (Config.isOnline){
            INDEX_PATH = "/root/doc_searcher_index/";
        }else {
            INDEX_PATH = "D:/doc_searcher_index/";
        }
    }

    private ObjectMapper objectMapper = new ObjectMapper();
    // 使用数组下包表示 docId
    private ArrayList<DocInfo> forwardIndex = new ArrayList<>();

    // 创建锁对象
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    //使用哈希表 来表示倒排索引
    // key 就是词
    // value 就是一组和这个词关联的文章
    private HashMap<String,ArrayList<Weight>> invertedIndex = new HashMap<>();
    // 这个类要提供的方法
    // 1.给定一个 docId，在正排索引中，查询文档的详细信息
    public DocInfo getDocInfo(int id){
        return forwardIndex.get(id);
    }

    // 2. 给定一个词，在倒排索引中，查哪些文档和这个词关联
    public List<Weight> getInverted(String term){
        return invertedIndex.get(term);
    }
    // 3. 往索引中新增一个文档
    public void addDoc(String title,String url,String content){
        // 往索引中新增一个文档
        // 需要同时给正排和倒排索引新增信息

        // 正排索引
        DocInfo docInfo = buildForward(title,url,content);
        // 倒排索引
        buildInverted(docInfo);
    }

    private void buildInverted(DocInfo docInfo) {
        //分词，针对标题和正文分词
        // 分词的结果，就知道当前文档id应该加入到 哪个倒排索引的 key 中
        // 倒排索引是一个键值对结构 （HashMap） ，key 分词结果（term）,value 是一组和这个分词结果相关的文档 id 列表
        // 因此就可以先针对当前文档进行分词，然后根绝每个分词的结果，去倒排索引中，找到对应的value,然后把当亲文档 id 给加入到 对应的value 列表中

        // 但是倒排索引的value 是一个 链表，此处通过词出现的次数，来表示相关性

        class  WordCnt{
            // 表示这个词在标题中出现的次数
            public int titleCount;

            // 表示这个词在正文中出现的次数
            public int contentCount;
        }
        // 这个数据结构用来表示词频
        HashMap<String,WordCnt> wordCntHashMap = new HashMap<>();

        // 1.先针对标题进行分词 (搜索引擎中不出现大小写)
        List<Term> terms = ToAnalysis.parse(docInfo.getTitle()).getTerms();


        // 2. 遍历分词结果，统计每个词出现的次数
        for (Term term: terms) {
            String word = term.getName();
            WordCnt wordCnt = wordCntHashMap.get(word);
            if(wordCnt == null){
                wordCnt = new WordCnt();
                wordCnt.titleCount = 1;
                wordCntHashMap.put(word,wordCnt);
            }else {
                wordCnt.titleCount++;
            }
        }
        // 3. 针对正文进行分词
        terms = ToAnalysis.parse(docInfo.getContent()).getTerms();
        // 4. 遍历分词结果，统计每个词出现的次数
        for (Term term: terms) {
            String word = term.getName();
            WordCnt wordCnt = wordCntHashMap.get(word);
            if(wordCnt == null){
                wordCnt = new WordCnt();
                wordCnt.contentCount = 1;
                wordCntHashMap.put(word,wordCnt);
            }else {
                wordCnt.contentCount++;
            }
        }
        // 5. 把上面的结果汇总到一个 HashMap 里面
        // 什么样的对象才能使用foreach  对象实现了 Iterable接口
        // Map 没有实现， Set 实现了 Iterable 就可以把Map 转换成 Set
        // Set 这里存的是一个把 键值对打包在一起的类 称为 Entry
        // 这样 就能变量了
        for (Map.Entry<String,WordCnt> entry : wordCntHashMap.entrySet()){
            // 获取关键字
            String word = entry.getKey();
            WordCnt wordCnt = entry.getValue();
            Weight weight = new Weight();
            weight.setDocId(docInfo.getDocId());
            synchronized (lock2){
                // 根据关键字获取倒排拉链
                ArrayList<Weight> invertedArrayList = invertedIndex.get(word);
                int wordWeight = wordCnt.titleCount * 10 + wordCnt.contentCount;
                weight.setWeight(wordWeight);
                if (invertedArrayList == null){
                    invertedArrayList = new ArrayList<>();
                    invertedArrayList.add(weight);
                    invertedIndex.put(word,invertedArrayList);
                }else {
                    invertedArrayList.add(weight);
                }
            }

        }

        //  最终文档的权重，就设定完成标题中出现的次数 * 10 + 正文中出现的次数

        // 遍历 hashMap ，依次更新倒排索引中的结构。

    }

    private DocInfo buildForward(String title, String url, String content) {
        DocInfo docInfo = new DocInfo();
        docInfo.setTitle(title);
        docInfo.setUrl(url);
        docInfo.setContent(content);
        synchronized (lock1){
            docInfo.setDocId(forwardIndex.size());
            forwardIndex.add(docInfo);
        }
        return docInfo;
    }

    // 4. 把内存中的索引结构保存到磁盘中
    public void save(){
        // 使用两个文件保存正排和倒排
        // 先判断索引对应的目录是否存在，不存在就创建
        long begin = System.currentTimeMillis();
        System.out.println("保存索引开始");
        File indexPathFile = new File(INDEX_PATH);
        if (!indexPathFile.exists()){
            indexPathFile.mkdirs();
        }
        File forwardIndexFile = new File(INDEX_PATH + "forward.txt");
        File invertedIndexFile = new File(INDEX_PATH + "inverted.txt");
        try {
            objectMapper.writeValue(forwardIndexFile,forwardIndex);
            objectMapper.writeValue(invertedIndexFile,invertedIndex);
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("保存索引成功，消耗的时间：" + (end - begin) + "ms！");
        // 保存索引结束
    }
    // 5. 把磁盘中的索引数据加载到内存中
    public void load(){
        long begin = System.currentTimeMillis();
        System.out.println("加载索引开始");
        File forwardIndexFile = new File(INDEX_PATH + "forward.txt");
        File invertedIndexFile = new File(INDEX_PATH + "inverted.txt");
        try {
            forwardIndex = objectMapper.readValue(forwardIndexFile, new TypeReference<ArrayList<DocInfo>>() {});
            invertedIndex = objectMapper.readValue(invertedIndexFile, new TypeReference<HashMap<String, ArrayList<Weight>>>() {});
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        long end = System.currentTimeMillis();
        System.out.println("加载索引成功，消耗时间：" + (end - begin) + "ms!");
    }

    public static void main(String[] args) {
//        searcher.Index index = new searcher.Index();
//        index.load();
//        System.out.println("索引加载完成");
    }

}
